package cn.ywang.spring.framework.web.servlet;

import cn.ywang.spring.framework.annotation.Controller;
import cn.ywang.spring.framework.annotation.RequestMapping;
import cn.ywang.spring.framework.annotation.RequestParam;
import cn.ywang.spring.framework.context.ApplicationContext;
import cn.ywang.spring.utils.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author BeatificWang
 * @date 2019/5/3 13:16
 */
public class DispatcherServlet extends HttpServlet {

	private ApplicationContext context;

	/**
	 * 上下文配置文件地址
	 */
	private static final String CONTEXT_LOCATION = "contextConfigLocation";

	/**
	 * List of HandlerMappings used by this servlet
	 */
	private List<HandlerMapping> handlerMappings = new ArrayList<>(2);

	/**
	 * 映射器与适配器关联关系
	 */
	private Map<HandlerMapping, HandlerAdapter> hmHandlerAdapters = new LinkedHashMap<>(2);

	/**
	 * List of ViewResolvers used by this servlet
	 */
	private List<ViewResolver> viewResolvers = new ArrayList<>(2);

	/**
	 * 处理Get请求
	 *
	 * @param req
	 * @param resp
	 * @throws ServletException
	 * @throws IOException
	 */
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		this.doPost(req, resp);
	}

	/**
	 * 处理Post请求
	 *
	 * @param req
	 * @param resp
	 * @throws ServletException
	 * @throws IOException
	 */
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		this.doDispatcher(req, resp);
	}

	/**
	 * 请求转发
	 *
	 * @param req
	 * @param resp
	 */
	private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
		try {
			// 根据用户请求的URL来获得一个handler
			HandlerMapping handlerMapping = this.getHandler(req);
			// 没有找到映射器，返回404
			if (handlerMapping == null) {
				this.processDispatchResult(req, resp, new ModelAndView("404"));
				return;
			}
			// 准备调用前的参数,获取适配器
			HandlerAdapter ha = this.getHandlerAdapter(handlerMapping);
			// 真正的调用方法,返回ModelAndView，存储要传页面名称及页面模版上的值
			ModelAndView mv = ha.handle(req, resp, handlerMapping);
			this.processDispatchResult(req, resp, mv);
		} catch (Exception e) {
			e.printStackTrace();
			Map<String, Object> errors = new LinkedHashMap<>(2);
			errors.put("error", e.getMessage());
			this.processDispatchResult(req, resp, new ModelAndView("500", errors));
		}
	}

	/**
	 * 处理转发请求
	 *
	 * @param req
	 * @param resp
	 * @param mv
	 */
	private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, ModelAndView mv) {
		// 将ModelAndView变成HTML/OutputStream/Json/FreeMarker/Velocity
		if (null == mv || this.viewResolvers.isEmpty()) {
			return;
		}
		for (ViewResolver viewResolver : this.viewResolvers) {
			View view = viewResolver.resolveViewName(mv.getViewName(), null);
			view.render(mv.getModel(), req, resp);
			return;
		}


	}

	/**
	 * 获取handler适配器
	 *
	 * @param handlerMapping
	 * @return
	 */
	private HandlerAdapter getHandlerAdapter(HandlerMapping handlerMapping) {
		HandlerAdapter handlerAdapter = this.hmHandlerAdapters.get(handlerMapping);
		if (handlerAdapter.supports(handlerMapping)) {
			return handlerAdapter;
		}
		return null;
	}

	/**
	 * 获取HandlerMapping
	 *
	 * @param req
	 * @return
	 */
	private HandlerMapping getHandler(HttpServletRequest req) {
		// uri资源地址
		String url = req.getRequestURI();
		// 全局地址
		String contextPath = req.getContextPath();
		url = url.replace(contextPath, "").replaceAll("/+", "/");
		for (HandlerMapping handlerMapping : this.handlerMappings) {
			Matcher matcher = handlerMapping.getPattern().matcher(url);
			if (!matcher.matches()) {
				continue;
			}
			return handlerMapping;
		}
		return null;
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		try {
			// 初始化ApplicationContext
			this.context = new ApplicationContext(config.getInitParameter(CONTEXT_LOCATION));
			// 初始化SpringMVC的九大组件
			this.initStrategies(this.context);
		} catch (Exception e) {
			throw new RuntimeException("初始化失败：" + e.toString());
		}
	}


	/**
	 * 初始化Spring MVC的九大策略
	 * <p>
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	private void initStrategies(ApplicationContext context) throws Exception {
		// 多文件上传
		this.initMultipartResolver(context);
		// 本地语言
		this.initLocaleResolver(context);
		// 模板化
		this.initThemeResolver(context);
		// url映射器
		this.initHandlerMappings(context);
		// 适配器
		this.initHandlerAdapters(context);
		// 请求异常处理
		this.initHandlerExceptionResolvers(context);
		// 请求转视图解析
		this.initRequestToViewNameTranslator(context);
		// 视图解析
		this.initViewResolvers(context);
		// FlashMap
		this.initFlashMapManager(context);
	}

	private void initFlashMapManager(ApplicationContext context) {
	}

	/**
	 * 初始化视图解析器
	 *
	 * @param context
	 */
	private void initViewResolvers(ApplicationContext context) throws Exception {
		// 模版所在目录
		String templateRoot = context.getConfig().getProperty("templateRoot");

		String templateRootPath = context.getClass().getClassLoader().getResource(templateRoot).getFile();
		File templateDir = new File(templateRootPath);
		File[] files = templateDir.listFiles();
		if (files == null) {
			return;
		}
		for (File file : files) {
			this.viewResolvers.add(new ViewResolver(file));
		}
	}

	private void initRequestToViewNameTranslator(ApplicationContext context) {
	}

	private void initHandlerExceptionResolvers(ApplicationContext context) {
	}

	/**
	 * request转换为handlerMapping
	 *
	 * @param context
	 */
	private void initHandlerAdapters(ApplicationContext context) {
		for (HandlerMapping hm : this.handlerMappings) {
			// 每一个方法有一个参数列表，这里保存的是形参及对应的顺序
			Map<String, Integer> paramMapping = new HashMap<>(2);

			// 处理命名参数
			Annotation[][] pa = hm.getMethod().getParameterAnnotations();
			for (int i = 0; i < pa.length; i++) {
				for (Annotation annotation : pa[i]) {
					if (annotation instanceof RequestParam) {
						String paramName = ((RequestParam) annotation).value();
						if (!StringUtils.isBlank(paramName)) {
							paramMapping.put(paramName, i);
						}
					}
				}
			}
			// TODO:处理未命名参数
			//			Method method = handlerMapping.getMethod();
			//			Parameter[] parameters = method.getParameters();
			//			for (Parameter parameter : parameters) {
			//				String paramName = parameter.getName();
			//				if (paramMapping.containsKey(paramName)) {
			//					continue;
			//				}
			//			}
			// 处理 request response
			Class<?>[] paramTypes = hm.getMethod().getParameterTypes();
			for (int i = 0; i < paramTypes.length; i++) {
				Class<?> type = paramTypes[i];
				if ((type == HttpServletRequest.class) || type == HttpServletResponse.class) {
					paramMapping.put(type.getName(), i);
				}
			}
			this.hmHandlerAdapters.put(hm, new HandlerAdapter(paramMapping));
		}
	}

	/**
	 * 初始化映射处理器
	 *
	 * @param context
	 */
	private void initHandlerMappings(ApplicationContext context) {
		String[] beanNames = context.getBeanDefinitionNames();
		Object controller;
		Class<?> clazz;
		try {
			for (String beanName : beanNames) {
				controller = context.getBean(beanName);
				clazz = controller.getClass();
				if (!clazz.isAnnotationPresent(Controller.class)) {
					continue;
				}
				// 类级别的@RequestMapping注解的URI地址
				String baseUrl = "";
				if (clazz.isAnnotationPresent(RequestMapping.class)) {
					RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
					baseUrl = requestMapping.value();
				}
				// 扫描所有的public方法
				Method[] methods = clazz.getMethods();
				HandlerMapping hm;
				for (Method method : methods) {
					if (!method.isAnnotationPresent(RequestMapping.class)) {
						continue;
					}
					RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
					// 方法级别的@RequestMapping注解的URI地址
					String methodUrl = requestMapping.value();
					String regex = ("/" + baseUrl + methodUrl.replaceAll("\\*", ".*")).replaceAll("/+", "/");
					Pattern pattern = Pattern.compile(regex);
					hm = new HandlerMapping();
					hm.setController(controller);
					hm.setPattern(pattern);
					hm.setMethod(method);
					this.handlerMappings.add(hm);
					System.out.println("Mapping: " + regex + "," + method);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}


	}

	private void initThemeResolver(ApplicationContext context) {
	}

	private void initLocaleResolver(ApplicationContext context) {
	}

	private void initMultipartResolver(ApplicationContext context) {
	}
}
